package com.log;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ConcurrentLinkedQueue;

public class MyLogger implements Runnable {
	// 日志信息队列，以实现多线程并发访问日志
	private ConcurrentLinkedQueue _logQueue = null;
	// 0:dev, 1:debug, 2:info, 3:error, 4:fatal
	private static int _level = 0;
	//绕接文件大小
	private static int _length = 1000000;
	//当前日志文件
	private static File _file = null;
	//转接日志文件
	private  File _fileother = null;
	// 日期格式化器
	private SimpleDateFormat _sdfLog = null;
	// 日志文件句柄
	private FileOutputStream _fp = null;
	// 用于将日志队列中的信息写入文件的线程
	private Thread _lt = null;
	// 全局唯一的实例
	private static MyLogger _globalInstance = null;
	//初始化标志 ture初始化成功
	private static boolean initflag = false;
	
	private String[] name = {"",""};

	void _log(String level, String msg) {
		Object obj[] = new Object[] {
			_sdfLog.format(new Date()), getCaller(), level, msg
		};
		String str = String.format("[%s] %-50s %7s : %s\n", obj);
		
		// 注意：文件推迟打开或者代打开失败时, 信息则输出到控制台 确保信息可见
		//此时信息还是保存到队列,在文件打开时写入,是日志完整输出,方便查看
		if ( null == _fp ) {
			System.out.println(str);
		}
		
		//如果文件打开不成功会释放队列内存,此时消息不再存入队列
		if ( null == _logQueue ) {
			// 不再入队列，直接返回
			return;
		}
		// 注意：为保证多线程执行效率，在此不直接写日志文件，而是将日志信息记录在日志队列中，由专门的线程写入日志文件
		_logQueue.offer(str);		
	}

	public void dev(String msg) {
		if (_level <= 0) {
			_log("Dev", msg);
		}
	}

	public void debug(String msg) {
		if (_level <= 1) {
			_log("Debug", msg);
		}
	}

	public void info(String msg) {
		if (_level <= 2) {
			_log("Info", msg);
		}
	}

	public void error(String msg) {
		if (_level <= 3) {
			_log("-Error-", msg);
		}
	}

	public void fatal(String msg) {
		if (_level <= 4) {
			_log("*Fatal*", msg);
		}
	}

	/**
	 * MyLogger
	 * @param path: Log file path
	 * @param level: 0:dev, 1:debug, 2:info, 3:error, 4:fatal
	 */
	private MyLogger () {	
		_sdfLog = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		_logQueue = new ConcurrentLinkedQueue();
	}
	
	public void setLogPath(String path, int level, int length) {
		if( initflag ){
			return;
		}
		//只能调一次
		initflag = true;
		
		_level = level;
		_length = length*1000000;
		
		try {
			//打开日志文件
			_file = new File(path);
			_fp = new FileOutputStream(_file, true);
		} catch (FileNotFoundException e) {
			fatal(e.getMessage());
			System.out.println("MyLogger fatal error - can't open log file: " + path);
			_file = null;
			
			//启用默认配置路径 *保证日志输出*
			// 取MyLogger本类的路径
			java.net.URL imgURL = MyLogger.class.getResource("");
			path = imgURL.getPath() + "MyLogger.log";
			_file = new File(path);
			try {
				// 打开日志文件
				_fp = new FileOutputStream(_file, true);
			} catch (FileNotFoundException ex) {
				fatal(ex.getMessage());
				System.out.println("MyLogger fatal error - open default log file fail : " + path);
				_fp = null;
				// 若日志文件未打开，则不启动写日志文件线程了(没有意义)，直接返回
				//并释放队列
				_logQueue.clear();
				_logQueue = null;
				return;
			}
		}
		name = _file.getName().split("\\.");
		//如果 队列为 null 则不启动线程 所有打印进入控制台 
		if(null ==_logQueue) {
			error("_logQueue is null!");
			return;
		}
		// 启动写日志文件线程
		_lt = new Thread(this);
		_lt.start();
		
	}
	//没有看到被调用
	protected void finalize() {
		// 退出时停止线程
		info("log is exit");
		//等待队列中日志完全打印
		while(!_logQueue.isEmpty()){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				error(e.getMessage());
			}
		}
		//停止打印线程
//		if (_lt != null) {
//			_lt.stop();
//		}
		_logQueue.clear();
		_logQueue = null;
		
		//关闭文件 此时会打印到控制台
		try {
			_fp.close();
			_fp = null;
		} catch (IOException e) {
			System.out.println(e.getMessage());
			_fp = null;
		}
		info("log is able to print in this time!!!");
	}
	
	// 将此日志实例设置为全局的，以便在任何地方通过MyLogger.getGlobal()获取全局的实例
//	public void setAsGlobal() {
//		_globalInstance = this;
//	}

	// 获取全局的实例，如果没有，则立即在本类的class文件相同的目录下，创建一个名为"MyLogger.log"的日志
	public static MyLogger getGlobal() {
		if (_globalInstance == null) {
			_globalInstance = new MyLogger();
		}
		return _globalInstance;
	}
	
	private static String getCaller() {   
		    StackTraceElement[] stack = (new Throwable()).getStackTrace();   
	        StackTraceElement ste = stack[3]; 
	        String[] classPath = ste.getClassName().split("\\.");
		    return Thread.currentThread().getId()+" "+ste.getLineNumber()+" "+classPath[classPath.length-1]+"::"+ste.getMethodName();   
	}  
	//日志转接 采用文件大小判断
	private boolean roundFile() {
		try {
			if(null !=_fp){
				_fp.close();
				_fp = null;
			}

			String date = new SimpleDateFormat("_yyyy-MM-dd_HH.mm.ss.").format(new Date());
			String destname = _file.getParent();
			System.out.println(destname);
			String rootPath = null;
			if( null == destname ){
				//说明是相对路径的顶层, 没有目录 取父目录是会抛异常
				destname =  name[0] + date + name[1];
			} else {
				//rootPath = _file.getParentFile().getParent();
				//System.out.println(rootPath);
				//如果是根路径 则自带 " \" 非跟路径需要加上 "\"
				//if(null == rootPath){
				destname = destname+File.separator;
				//}
				destname =destname + name[0] + date + name[1];
				}

			_fileother = new File(destname);
			if( !_file.renameTo(_fileother) ){
				System.out.println("重命名文件失败,文件名：" + destname);
				destname = "temp_" + date+".log";
				if( !_file.renameTo(_fileother)){
					System.out.println("第二次尝试重命名文件失败,文件名：" + destname);
				}
			}
			_fileother = null;
			//FileOutputStream 会创建新的文件
			_fp = new FileOutputStream(_file, true);
			_fp.write(("----------create new log file -- "+date+"------------\n").getBytes());
		} catch (IOException ex) {
			ex.printStackTrace();
			//fatal(ex.getMessage());
			System.out.println("MyLogger fatal error - roundFile failed!");
			//文件为空时打印到 控制台
			_fp = null;
			_logQueue.clear();
			//导致线程结束
			_logQueue = null;
			return false;
		}
		return true;
	}

	// 写日志文件线程函数
	public void run() {
		info("MyLogger::Log thread started !");
		
		while (null != _logQueue) {
			try{
				while (!_logQueue.isEmpty()) {
					// 从队列尾取出日志信息
					String msg = (String) _logQueue.poll();
					// 将消息写入日志文件
					if( !_file.exists()){
						roundFile();
						info("log file not found!");
					}
					if (msg != null && _fp != null) {
						try {
							_fp.write(msg.getBytes());
							_fp.flush();
						} catch (IOException e) {
							error(e.getMessage());
						}
					} else if( null == _fp ) {
						//fatal("msg != null && _fp != null");
						System.out.println("_fp is null");
					}
					if(_file.length() > _length ){
						roundFile();
					}
				}
	
				try {
					// 等待一会儿再查询，否则CPU占用率会很高
					Thread.sleep(500);
					
				} catch (InterruptedException e) {
					e.printStackTrace();
					System.out.println("log run to sleep -------error");
				}
			} catch (Exception e) {
				//error(e.getMessage());
				e.printStackTrace();
				System.out.println("log run ----------------------error");
			}
		}
	}
	
	// 获取本类所在的目录
	public String getHomePath() 
	{
		java.net.URL imgURL = MyLogger.class.getResource("");
		String path = imgURL.getPath();
		return path;
	}
	
//for test only
//	public static void main(String[] args) {
//		MyLogger log = new MyLogger("c:\\test0.log", 0);
//
//		log.dev("test");
//		log.debug("你好abc!");
//		log.info("test");
//		log.error("你好abc!");
//		log.fatal("test");
//
//		MyLogger log1 = new MyLogger("c:\\test1.log", 1);
//
//		log1.dev("test");
//		log1.debug("你好abc!");
//		log1.info("test");
//		log1.error("你好abc!");
//		log1.fatal("test");
//
//		MyLogger log2 = new MyLogger("c:\\test2.log", 2);
//
//		log2.dev("test");
//		log2.debug("你好abc!");
//		log2.info("test");
//		log2.error("你好abc!");
//		log2.fatal("test");
//
//		MyLogger log3 = new MyLogger("c:\\test3.log", 3);
//
//		log3.dev("test");
//		log3.debug("你好abc!");
//		log3.info("test");
//		log3.error("你好abc!");
//		log3.fatal("test");
//
//		MyLogger log4 = new MyLogger("c:\\test4.log", 4);
//
//		log4.dev("test");
//		log4.debug("你好abc!");
//		log4.info("test");
//		log4.error("你好abc!");
//		log4.fatal("test");
//	}
}
